#!/usr/bin/env python

# Copyright Jamie Iles, 2017
#
# This file is part of s80x86.
#
# s80x86 is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# s80x86 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with s80x86.  If not, see <http://www.gnu.org/licenses/>.

import cmd
from shlex import split
import distorm3

from py8086sim.Cpu import JTAGCPU, GPR, Flag

regdict = {
    'AX': GPR.AX,
    'CX': GPR.CX,
    'DX': GPR.DX,
    'BX': GPR.BX,
    'SP': GPR.SP,
    'BP': GPR.BP,
    'SI': GPR.SI,
    'DI': GPR.DI,
    'ES': GPR.ES,
    'CS': GPR.CS,
    'SS': GPR.SS,
    'DS': GPR.DS,
    'IP': GPR.IP
}


class Debugger(cmd.Cmd):
    def __init__(self):
        cmd.Cmd.__init__(self)
        self.c = JTAGCPU("debugger")
        self._update_prompt()

    def _update_prompt(self):
        self.prompt = 'debug {0:04x}:{1:04x}> '.format(
            self.c.read_reg(GPR.CS), self.c.read_reg(GPR.IP))

    def do_reset(self, line):
        '''Reset the CPU'''
        self.c.reset()
        self._update_prompt()
        return False

    def do_disassemble(self, line):
        '''Disassemble the instruction at CS:IP'''
        tokens = split(line)
        if len(tokens) == 1:
            cs, ip = tokens[0].split(':')
            cs = int(cs, 16)
            ip = int(ip, 16)
        else:
            cs = self.c.read_reg(GPR.CS)
            ip = self.c.read_reg(GPR.IP)

        buf = [chr(self.c.read_mem8(cs, ip + i)) for i in xrange(15)]
        instruction = distorm3.Decode(ip, ''.join(buf), distorm3.Decode16Bits)[0]
        _, size, instruction, _ = instruction
        hexbytes = ['{0:02x}'.format(ord(b)) for b in buf[:size]]
        print('{0:s}\t{1:s}'.format(' '.join(hexbytes), instruction.lower()))

        return False

    def do_regs(self, line):
        '''Display the CPU registers'''
        regnames = ['AX', 'CX', 'DX', 'BX', 'SP', 'BP', 'SI', 'DI', 'ES',
                    'CS', 'SS', 'DS', 'IP']
        for name in regnames:
            print('{name}: {val:04x}'.format(name=name,
                                             val=self.c.read_reg(GPR.names[name])))
        flagnames = ['CF', 'PF', 'AF', 'ZF', 'SF', 'TF', 'IF', 'DF', 'OF']
        flags = self.c.read_flags()
        set_flags = filter(lambda x: flags & Flag.names[x], flagnames)
        print('FLAGS: {0:04x} {1:s}'.format(self.c.read_flags(),
                                            ' '.join(set_flags)))
        return False

    def do_step(self, line):
        '''Single step the CPU'''
        self.c.step()
        self._update_prompt()
        return False

    def do_write_reg(self, line):
        '''Write a GPR

        REG := VAL'''
        tokens = split(line)
        assert len(tokens) == 2
        self.c.write_reg(regdict[tokens[0]], int(tokens[1], 16))
        self._update_prompt()
        return False

    def do_in16(self, line):
        '''Read a 16-bit IO port'''
        tokens = split(line)
        assert len(tokens) == 1
        print('{0:04x}'.format(self.c.read_io16(int(tokens[0], 16))))
        self._update_prompt()
        return False

    def do_out16(self, line):
        '''Write a 16-bit IO port'''
        tokens = split(line)
        assert len(tokens) == 2
        self.c.write_io16(int(tokens[0], 16), int(tokens[1], 16))
        self._update_prompt()
        return False

    def do_in8(self, line):
        '''Read a 8-bit IO port'''
        tokens = split(line)
        assert len(tokens) == 1
        print('{0:04x}'.format(self.c.read_io8(int(tokens[0], 16))))
        self._update_prompt()
        return False

    def do_out8(self, line):
        '''Write a 8-bit IO port'''
        tokens = split(line)
        assert len(tokens) == 2
        self.c.write_io8(int(tokens[0], 16), int(tokens[1], 16))
        self._update_prompt()
        return False

    def do_w16(self, line):
        '''Write a 16-bit value to memory'''
        tokens = split(line)
        assert len(tokens) == 2
        seg, offset = tokens[0].split(':')
        self.c.write_mem16(int(seg, 16), int(offset, 16), int(tokens[1], 16))
        self._update_prompt()
        return False

    def do_r16(self, line):
        '''Read a 16-bit value from memory'''
        tokens = split(line)
        assert len(tokens) == 1
        seg, offset = tokens[0].split(':')
        v = self.c.read_mem16(int(seg, 16), int(offset, 16))
        print('{0:04x}'.format(v))
        self._update_prompt()
        return False

    def do_EOF(self, line):
        print('')
        return True

Debugger().cmdloop()
